package ru.arriah.common.web;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Properties;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import ru.arriah.common.web.action.ActionMapping;
import ru.arriah.common.web.action.WebAction;
import ru.arriah.common.web.exception.IllegalActionException;

public abstract class UIController extends GenericHttpServlet {
   protected HashMap<String, ActionMapping> actionMappings;
   protected Class actionsEnum;
   protected RequestDispatcher templateDispatcher;

   public UIController() {
      super();
      actionMappings = null;
      actionsEnum = null;
      templateDispatcher = null;
   }

   public void init(ServletConfig config) throws ServletException {
      super.init(config);
      String templatePage = config.getInitParameter("templatePage");

      templateDispatcher =
         getServletContext().getRequestDispatcher(templatePage);

      if (templateDispatcher == null) {
         throw new ServletException(
            "Can't find template page: \"" + templatePage + "\""
         );
      }

      String actionsEnumName = config.getInitParameter("actionsEnumName");

      try {
         actionsEnum = Class.forName(actionsEnumName);
      } catch (Exception e) {
         handleException(e, "init");
         throw new ServletException(e);
      }

      if (!actionsEnum.isEnum()) {
         throw new ServletException(
            actionsEnumName + " should be of enum type"
         );
      }

      initializeActionMappings(
         (Enum[]) actionsEnum.getEnumConstants(), loadMappings(), loadConfig()
      );
   }

   protected Properties loadMappings() throws ServletException {
      String mappingsLocation =
         getServletConfig().getInitParameter("mappingsLocation");

      InputStream inputStream =
         getServletContext().getResourceAsStream(mappingsLocation);

      if (inputStream == null) {
         throw new ServletException(
            "Illegal mappings location: \"" + mappingsLocation + "\""
         );
      }

      Properties mappings = new Properties();

      try {
         mappings.load(inputStream);
         inputStream.close();
      } catch (Exception e) {
         handleException(e, "loadMappings");
         throw new ServletException(e);
      }

      return mappings;
   }

   protected Properties loadConfig() throws ServletException {
      String configLocation =
         getServletConfig().getInitParameter("configLocation");

      InputStream inputStream =
         getServletContext().getResourceAsStream(configLocation);

      if (inputStream == null) {
         throw new ServletException(
            "Illegal config location: \"" + configLocation + "\""
         );
      }

      Properties configs = new Properties();

      try {
         configs.load(inputStream);
         inputStream.close();
      } catch (Exception e) {
         handleException(e, "loadConfig");
         throw new ServletException(e);
      }

      return configs;
   }

   protected void initializeActionMappings(
      Enum[] actions, Properties mappings, Properties config
   ) throws ServletException {
      actionMappings = new HashMap<String, ActionMapping>();

      for (Enum action: actions) {
         String keyPrefix = action + ".";
         String handlerTypeKey = keyPrefix + "handlerType";
         String successTargetKey = keyPrefix + "successTarget";
         String failureTargetKey = keyPrefix + "failureTarget";
         ActionMapping mapping = new ActionMapping();
         mapping.setActionName(action.toString());
         String handlerTypeName = mappings.getProperty(handlerTypeKey);

         try {
            Class handlerType = Class.forName(handlerTypeName);
            mapping.setHandlerType(handlerType);

            WebAction webAction = (WebAction)handlerType.newInstance();
            webAction.setConfig(config);
            mapping.setActionHandler(webAction);

         } catch (Exception e) {
            handleException(e, "initializeActions");
            throw new ServletException(e);
         }

         String successTarget = mappings.getProperty(successTargetKey);
         mapping.setSuccessTarget(successTarget);
         String failureTarget = mappings.getProperty(failureTargetKey);

         mapping.setFailureTarget(
            failureTarget == null ? successTarget : failureTarget
         );

         if (!mapping.isValid()) {
            throw new ServletException(
               "Mapping is invalid, " +
               "action name - \"" + action + "\", " +
               "error message - " + mapping.getErrorMessage()
            );
         }

         actionMappings.put(action.toString(), mapping);
      }
   }

   @SuppressWarnings(value = {"unchecked"})
   protected String getActionName(
      HttpServletRequest request
   ) throws IllegalActionException {
      try {
         String actionName = request.getParameter("_action");
         Enum.valueOf(actionsEnum, actionName);
         return actionName;
      } catch (Exception e) {
         handleException(e, "getActionName");
         throw new IllegalActionException("ErrorMessage.illegalAction");
      }
   }

   protected abstract void prologue(
      ActionMapping actionMapping,
      HttpServletRequest request,
      HttpServletResponse response
   ) throws IOException, ServletException;

   protected abstract void epilogue(
      ActionMapping actionMapping,
      HttpServletRequest request,
      HttpServletResponse response
   ) throws IOException, ServletException;

   protected void process(
      HttpServletRequest request, HttpServletResponse response
   ) throws IOException, ServletException {
      String actionName = getActionName(request);
      ActionMapping actionMapping = actionMappings.get(actionName);
      ActionMapping originalActionMapping = actionMapping;
      WebAction actionHandler = actionMapping.getActionHandler();
      prologue(originalActionMapping, request, response);

      request.setAttribute(
         "page", actionHandler.execute(actionMapping, request, response)
      );

      epilogue(originalActionMapping, request, response);
      templateDispatcher.forward(request, response);
   }

   public void doGet(
      HttpServletRequest request, HttpServletResponse response
   ) throws IOException, ServletException {
      process(request, response);
   }

   public void doPost(
      HttpServletRequest request, HttpServletResponse response
   ) throws IOException, ServletException {
      process(request, response);
   }
}
